﻿using UnityEngine.Pool;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace UnityEngine.UI
{
    public interface IUIAdditionData
    {
        AdditionalCanvasShaderChannels DataChannels { get; }
        void BeforeApplyVerts(UIAdditionalData addData);
        void ApplyVert(ref UIVertex vert);
    }

    [AddComponentMenu("UI/Effects/UI Additional Data", 80)]
    /// <summary>
    /// Adds an outline to a graphic using IVertexModifier.
    /// </summary>
    public class UIAdditionalData : BaseMeshEffect
    {
        [SerializeField, Tooltip("生成单位化UV(uv0.zw)")]
        bool m_GenerateNormalizedUv;

        [SerializeField, Tooltip("生成像素化UV(uv1.xy)")]
        bool m_GeneratePixelUv;

        [SerializeField, Tooltip("增益亮度 (uv1.z)")]
        float m_Intensity = 0f;

        [SerializeField, Tooltip("遮罩区域")]
        RectTransform m_MaskRect;

        IUIAdditionData[] mAdds;

        public float Intensity
        {
            get { return m_Intensity; }
            set
            {
                var intensity = Mathf.Max(-1, value);
                if (!Mathf.Approximately(m_Intensity, intensity))
                {
                    m_Intensity = intensity;
                    graphic?.SetVerticesDirty();
                }
            }
        }

        public RectTransform MaskRect
        {
            get { return m_MaskRect; }
            set
            {
                if(m_MaskRect != value)
                {
                    m_MaskRect = value;
                    graphic?.SetVerticesDirty();
                }
            }
        }

        public void SetDirty()
        {
            graphic?.SetVerticesDirty();
        }

        protected UIAdditionalData()
        { }

        protected override void Awake()
        {
            base.Awake();
            mAdds = GetComponents<IUIAdditionData>();
        }

        public override void ModifyMesh(VertexHelper vh)
        {
            if (!IsActive())
                return;
            var len = mAdds == null ? 0 : mAdds.Length;
            AdditionalCanvasShaderChannels channels = 0;
            for (int i = 0; i < len; i++)
            {
                if (mAdds[i] != null)
                    channels |= mAdds[i].DataChannels;
            }
            var genUv = m_GenerateNormalizedUv || m_MaskRect != null;
            var genUv1 = m_GeneratePixelUv || !Mathf.Approximately(m_Intensity, 0);
            if (channels == 0 && !genUv1 && !genUv)
                return;

            Rect rect;
            if (m_MaskRect != null)
            {
                var matrix = transform.worldToLocalMatrix * m_MaskRect.localToWorldMatrix;
                rect = m_MaskRect.rect;
                var min = matrix.MultiplyPoint(rect.min);
                var max = matrix.MultiplyPoint(rect.max);
                rect = new Rect(min, max - min);
            }
            else
            {
                rect = ((RectTransform)transform).rect;
            }
            float pixelScale = 0.01f;
            if (m_GeneratePixelUv && graphic is Image img)
            {
                pixelScale = img.canvas.referencePixelsPerUnit;
                pixelScale = pixelScale > 0 ? (1f / pixelScale) : 0.01f;
            }
            var size = rect.size;
            var dsize = new Vector2(size.x > 0 ? (1f / size.x) : size.x, size.y > 0 ? (1f / size.y) : size.y);
            var intensity = Mathf.Max(-1, m_Intensity);
            for (int i = 0; i < len; i++)
            {
                mAdds[i]?.BeforeApplyVerts(this);
            }
            var output = ListPool<UIVertex>.Get();
            vh.GetUIVertexStream(output);
            for (int i = 0; i < output.Count; i++)
            {
                var vert = output[i];
                if (genUv)
                {
                    vert.uv0.z = (vert.position.x - rect.xMin) * dsize.x;
                    vert.uv0.w = (vert.position.y - rect.yMin) * dsize.y;
                }
                if (genUv1)
                {
                    vert.uv1.x = (vert.position.x - rect.xMin) * pixelScale;
                    vert.uv1.y = (vert.position.y - rect.yMin) * pixelScale;
                    vert.uv1.z = intensity;
                }
                for (int k = 0; k < len; k++)
                {
                    mAdds[k]?.ApplyVert(ref vert);
                }
                output[i] = vert;
            }
            vh.Clear();
            vh.AddUIVertexTriangleStream(output);
            ListPool<UIVertex>.Release(output);
        }

#if UNITY_EDITOR
        private void OnDrawGizmosSelected()
        {
            if(m_MaskRect != null)
            {
                Gizmos.matrix = m_MaskRect.localToWorldMatrix;
                Gizmos.color = Color.black;
                Gizmos.DrawWireCube(m_MaskRect.rect.center, m_MaskRect.rect.size);
            }
        }

        protected override void OnValidate()
        {
            base.OnValidate();
            mAdds = GetComponents<IUIAdditionData>();
            AdditionalCanvasShaderChannels channels = 0;
            if (m_GeneratePixelUv || !Mathf.Approximately(m_Intensity, 0))
                channels |= AdditionalCanvasShaderChannels.TexCoord1;
            for (int i = 0; i < mAdds.Length; i++)
            {
                channels |= mAdds[i].DataChannels;
            }
            if (channels != 0)
            {
                var canvas = GetComponentInParent<Canvas>();
                if (canvas != null)
                {
                    var add = canvas.additionalShaderChannels;
                    canvas.additionalShaderChannels |= channels;
                    if (add != canvas.additionalShaderChannels)
                        EditorUtility.SetDirty(canvas);
                }
            }
        }

#endif
    }
}
